#!/bin/sh

LOCK_DURATION="${LOCK_DURATION:-60}"
LOCK_SLEEP="${LOCK_SLEEP:-5}"
QUEUE_NAME=create_event.pipe
RETRY_DELAY="${RETRY_DELAY:-1000}"
RETRY_LIMIT="${RETRY_LIMIT:-10}"

SHELL="$(readlink /proc/$$/exe || true)"
if ! command -v "$SHELL" >/dev/null 2>&1 \
  || [ "x$(basename "$SHELL")" = x ] \
  || [ "x$(basename "$SHELL")" = xbusybox ]
then
  SHELL=sh
fi
SHELL_XTRACE=$(! echo $- | grep -q x || echo " -x")

acquire_lock() {
  try_lock true
}

maintain_lock() {
  while true
  do
    sleep "$LOCK_SLEEP"
    try_lock false
    if [ "$?" != 0 ]
    then
      return 1
    fi
  done
}

try_lock() {
  if [ -z "$CLUSTER_NAMESPACE" -o -z "$POD_NAME" -o -z "$SERVICE_ACCOUNT" \
    -o -z "$LOCK_RESOURCE" -o -z "$LOCK_RESOURCE_NAME" ]
  then
    echo "CLUSTER_NAMESPACE, SERVICE_ACCOUNT, POD_NAME, LOCK_RESOURCE and LOCK_RESOURCE_NAME environmant variables must be defined"
    return 1
  fi
  local AQUIRE="$1"
  local TEMPLATE='
  LOCK_POD={{ if .metadata.annotations }}{{ if (index .metadata.annotations "'"$LOCK_POD_KEY"'") }}{{ (index .metadata.annotations "'"$LOCK_POD_KEY"'") }}{{ else }}{{ end }}{{ else }}{{ end }}
  LOCK_TIMEOUT={{ if .metadata.annotations }}{{ if (index .metadata.annotations "'"$LOCK_TIMEOUT_KEY"'") }}{{ (index .metadata.annotations "'"$LOCK_TIMEOUT_KEY"'") }}{{ else }}0{{ end }}{{ else }}0{{ end }}
  RESOURCE_VERSION={{ .metadata.resourceVersion }}
  '
  kubectl get "$LOCK_RESOURCE" -n "$CLUSTER_NAMESPACE" "$LOCK_RESOURCE_NAME" --template="$TEMPLATE" > /tmp/lock-resource
  . /tmp/lock-resource
  CURRENT_TIMESTAMP="$(date +%s)"
  if [ "$POD_NAME" != "$LOCK_POD" ]
  then
    if "$AQUIRE"
    then
      if [ "$CURRENT_TIMESTAMP" -lt "$LOCK_TIMEOUT" ]
      then
        local WAIT_DURATION="$((LOCK_DURATION + LOCK_SLEEP))"
        echo "Locked already by $LOCK_POD until $(date -d @"$LOCK_TIMEOUT" -Iseconds --utc), will retry in $WAIT_DURATION seconds"
        sleep "$WAIT_DURATION"
        try_lock true
      fi
    else
      echo "Locked already by $LOCK_POD until $(date -d @"$LOCK_TIMEOUT" -Iseconds --utc)"
      return 1
    fi
  else
    if ! "$AQUIRE" && [ "$CURRENT_TIMESTAMP" -gt "$LOCK_TIMEOUT" ]
    then
      echo "Lock expired!"
      return 1
    fi
  fi
  if ! kubectl annotate "$LOCK_RESOURCE" -n "$CLUSTER_NAMESPACE" "$LOCK_RESOURCE_NAME" \
    --resource-version "$RESOURCE_VERSION" --overwrite \
    "$LOCK_SERVICE_ACCOUNT_KEY=$SERVICE_ACCOUNT" "$LOCK_POD_KEY=$POD_NAME" "$LOCK_TIMEOUT_KEY=$((CURRENT_TIMESTAMP + LOCK_DURATION))"
  then
    kubectl get "$LOCK_RESOURCE" -n "$CLUSTER_NAMESPACE" "$LOCK_RESOURCE_NAME" --template="$TEMPLATE" > /tmp/lock-resource
    . /tmp/lock-resource
    if [ "$POD_NAME" = "$LOCK_POD" ]
    then
      try_lock "$AQUIRE"
      return 0
    fi
    echo "Locked by $LOCK_POD until $(date -d @"$LOCK_TIMEOUT" -Iseconds --utc)"
    if "$AQUIRE"
    then
      sleep "$((LOCK_SLEEP * 4))"
      try_lock true
    else
      return 1
    fi
  fi
}

release_lock() {
  if [ -z "$CLUSTER_NAMESPACE" -o -z "$SERVICE_ACCOUNT" -o -z "$POD_NAME" \
    -o -z "$LOCK_RESOURCE" -o -z "$LOCK_RESOURCE_NAME" ]
  then
    echo "CLUSTER_NAMESPACE, SERVICE_ACCOUNT, POD_NAME, LOCK_RESOURCE and LOCK_RESOURCE_NAME environmant variables must be defined"
    return 1
  fi
  local TEMPLATE='
  LOCK_POD={{ if and .metadata.annotations (index .metadata.annotations "'"$LOCK_POD_KEY"'") }}{{ (index .metadata.annotations "'"$LOCK_POD_KEY"'") }}{{ else }}{{ end }}
  LOCK_TIMEOUT={{ if and .metadata.annotations (index .metadata.annotations "'"$LOCK_TIMEOUT_KEY"'") }}{{ (index .metadata.annotations "'"$LOCK_TIMEOUT_KEY"'") }}{{ else }}0{{ end }}
  RESOURCE_VERSION={{ .metadata.resourceVersion }}
  '
  kubectl get "$LOCK_RESOURCE" -n "$CLUSTER_NAMESPACE" "$LOCK_RESOURCE_NAME" --template="$TEMPLATE" > /tmp/lock-resource
  . /tmp/lock-resource
  if [ "$POD_NAME" != "$LOCK_POD" ]
  then
    return 0
  fi
  if ! kubectl annotate "$LOCK_RESOURCE" -n "$CLUSTER_NAMESPACE" "$LOCK_RESOURCE_NAME" \
    --resource-version "$RESOURCE_VERSION" --overwrite "$LOCK_SERVICE_ACCOUNT_KEY-" "$LOCK_POD_KEY-" "$LOCK_TIMEOUT_KEY-"
  then
    kubectl get "$LOCK_RESOURCE" -n "$CLUSTER_NAMESPACE" "$LOCK_RESOURCE_NAME" --template="$TEMPLATE" > /tmp/lock-resource
    . /tmp/lock-resource
    if [ "$POD_NAME" = "$LOCK_POD" ]
    then
      release_lock
    fi
    return 0
  fi
}

to_json_string() {
  jq -s -R .
}

date_iso8601() {
  date +%Y-%m-%dT%H:%M:%SZ --utc
}

create_event() {
  create_event_try "$@" || true
}

create_event_try() {
  local REASON="$1"
  local TYPE="$2"
  local MESSAGE="$3"
  local EVENT_ID="$(get_event_id)"
  local EVENT_NAME="$RESOURCE_NAME.$EVENT_ID"
  local EVENT_TIMESTAMP="$(date_iso8601)"
  local COMPONENT="$OP_NAME"
  local RESULT

  echo "Creating event with reason '$REASON' of type '$TYPE' with message '$MESSAGE'"
  RESULT="$({ kubectl get "$RESOURCE_CRD_NAME" -n "$CLUSTER_NAMESPACE" "$RESOURCE_NAME" -o json || printf .; } | jq -c .)"
  MESSAGE="$(printf '%s' "$MESSAGE" | jq -s -R .)"
  RESULT="$(printf '%s' "$RESULT" | jq -c '{
      apiVersion: "v1",
      type: "'"$TYPE"'",
      reason: "'"$REASON"'",
      message: '"$MESSAGE"',
      metadata: {
        name: "'"$EVENT_NAME"'",
        namespace: "'"$CLUSTER_NAMESPACE"'",
        labels: .metadata.labels
      },
      count: 1,
      firstTimestamp: "'"$EVENT_TIMESTAMP"'",
      lastTimestamp: "'"$EVENT_TIMESTAMP"'",
      source: {
        component: "'"$COMPONENT"'"
      },
      involvedObject: {
        apiVersion: .apiVersion,
        kind: .kind,
        namespace: .metadata.namespace,
        name: .metadata.name,
        resourceVersion: .metadata.resourceVersion,
        uid: .metadata.uid
      }
    }')"
  printf '%s' "$RESULT" | kubectl create --raw "/api/v1/namespaces/$CLUSTER_NAMESPACE/events" -f -
}

get_event_id() {
  local MILLIS_SINCE_EPOCH="$(date +%s%N | cut -b1-13)"
  local RANDOM_LONG="$(get_random_long)"
  local HEX_MILLIS="$(printf '%x\n' "$MILLIS_SINCE_EPOCH")"
  local HEX_RANDOM_LONG="$(printf '%x\n' "$RANDOM_LONG")"
  echo "$HEX_MILLIS$HEX_RANDOM_LONG"
}

get_random_long() {
  cat /dev/urandom | tr -dc '0-9' | head -c 15 | sed 's/^0\+//'
}

from_date_iso8601_to_unix_timestamp() {
  [ -n "$1" ]
  date -d "$1" +%s
}

is_child() {
  local PID="$1"
  grep -q "^PPid:[[:space:]]$$" "/proc/$PID/status" 2>/dev/null
}

kill_with_childs() {
  (
  [ "$DEBUG" = true ] || set +x
  set +e
  local PID="$1"
  local SPIDS="x"
  local OPIDS
  OPIDS="$PID"
  local OPPID="$OPIDS"
  while [ -n "$OPPID" ]
  do
      OPPID="$(grep '^PPid:[[:space:]]'"\($(
            echo "$OPPID" | sed '{s/ $//;s/ /\\|/g}'
          )\)"'$' /proc/[0-9]*/status 2>/dev/null \
        | cut -d / -f 3 | tr '\n' ' ')"
      OPIDS="$OPIDS $OPPID"
  done
  kill -13 $OPIDS 2>/dev/null || true
  )
}

kill_session_siblings() {
  (
  [ "$DEBUG" = true ] || set +x
  set +e
  local PID="$(exec "$SHELL" -c 'echo $PPID')"
  local NSUID="$(id -u)"
  local NSSID="$(grep '^NSsid:[[:space:]]' "/proc/$PID/status" \
        | tr -d '[:space:]' | cut -d : -f 2)"
  local SPIDS="x"
  local OPIDS
  OPIDS="$PID"
  local OPPID="$OPIDS"
  while [ "$OPPID" != "$NSSID" ]
  do
      OPPID="$(grep '^PPid:[[:space:]]' "/proc/$OPPID/status" \
        | tr -d '[:space:]' | cut -d : -f 2)"
      OPIDS="$OPIDS $OPPID"
  done
  while [ "$SPIDS" = x ] \
    || [ "$(ls -d $(echo "$SPIDS" | sed 's#\([0-9]\+\) #/proc/\1 #g') 2>&1 | grep -i 'no such' | wc -l)" \
      -lt "$(echo "$SPIDS" | wc -w)" ]
  do
    test "$SPIDS" = "x" || kill -13 $SPIDS 2>/dev/null || true
    SPIDS="$(grep '\(^Uid:[[:space:]]\+'"$NSUID"'[[:space:]]\|^NSsid:[[:space:]]'"$NSSID"'$\)' -c /proc/[0-9]*/status 2>/dev/null \
      | grep ':2$' | cut -d / -f 3 | grep -v '^'"\($(echo "$OPIDS" | sed 's/ /\\|/g')\)"'$' | tr '\n' ' ')"
  done
  ) || true
}

trap_callback() {
  kill_session_siblings
}

trap_callback_and_exit() {
  trap_callback
  exit "$1"
}

set_trap() {
  trap 'trap_callback_and_exit $?' HUP INT QUIT PIPE TERM ABRT
  trap 'trap_callback $?' EXIT
}

create_event_queue() {
  local PIPELINE_MODE="p"
  rm -f "$SHARED_PATH/$QUEUE_NAME" 
  mknod "$SHARED_PATH/$QUEUE_NAME" "$PIPELINE_MODE"
}

create_event_service() {
  echo "$1" "$2" "$(printf %s "$3" | base64 -w 0)" >> "$SHARED_PATH/$QUEUE_NAME"
}

read_events_service_loop() {
  while true
  do
    read_events_service
  done
}

read_events_service() {
  while read -r REASON TYPE MESSAGE
  do
    create_event "$REASON" "$TYPE" "$(printf %s "$MESSAGE" | base64 -d)"
  done < "$SHARED_PATH/$QUEUE_NAME"
}

try_function_with_output() {
  (
    set +e
    (
    { { { "$@" 2>&1; echo $? >&3; } | tee "$SHARED_PATH/output" >&4; }  3>&1 | { read EXIT_CODE; exit "$EXIT_CODE"; }; } 4>&1
    )
    printf %s "$?" > "$SHARED_PATH/exit_code"
  )
}

retry() {
  local RETRY=0
  local BACKOFF
  until "$@"
  do
    if [ "$RETRY" -ge "$RETRY_LIMIT" ]
    then
      >&2 echo "Failed $@ after retrying $RETRY_LIMIT times"
      return 1
    fi
    BACKOFF="$(( (RETRY_DELAY << RETRY > 60 * 1000 ? 60 * 1000 : RETRY_DELAY << RETRY) / 1000 ))"
    >&2 echo "Will retry command after backoff of $BACKOFF seconds ($((RETRY_LIMIT - RETRY)) retry left)"
    sleep "$BACKOFF"
    RETRY="$((RETRY+1))"
    sleep 5
  done      
}
